package com.bolt.support.sqlconfig.builder;

import com.bolt.common.reflect.ClassLoaderUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.common.xml.XNode;
import com.bolt.convention.exception.ServiceException;
import com.bolt.support.query.calculator.domain.PivotModel;
import com.bolt.support.query.calculator.domain.UnpivotModel;
import com.bolt.support.sqlconfig.base.*;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class XMLStatementBuilder {

    private final XmlSqlConfigBuilder sqlConfigBuilder;

    public XMLStatementBuilder(XmlSqlConfigBuilder sqlConfigBuilder) {
        this.sqlConfigBuilder = sqlConfigBuilder;
    }

    public SqlStatement parseStatementNode(XNode context) throws ServiceException {
        try {
            String id = context.getStringAttribute("id");
            String sqlType = context.getStringAttribute("sqlType");
            String entityName = context.getStringAttribute("entity");
            boolean queryLimit = context.getBooleanAttribute("queryLimit", false);
            String tableKey = context.getStringAttribute("tableKey");
            SqlStatement sqlStatement = new SqlStatement();
            sqlStatement.setId(id);
            sqlStatement.setSqlType(sqlType);
            sqlStatement.setQueryLimit(queryLimit);
            sqlStatement.setTableKey(tableKey);
            if (StrUtil.isNotBlank(entityName)) {
                Class entity = ClassLoaderUtil.loadClass(entityName);
                sqlStatement.setEntity(entity);
            }
            List<SqlNode> contents = parseDynamicTags(context, sqlStatement);
            MixedSqlNode mixedSqlNodes = new MixedSqlNode(contents);
            sqlStatement.setRootAqlNode(mixedSqlNodes);
            return sqlStatement;
        } catch (Exception ex) {
            throw new ServiceException(ex);
        }

    }

    public void parseGlobalSqlNode(XNode context) {
        NodeList children = context.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            context.newXNode(children.item(i));
        }
    }

    private List<SqlNode> parseDynamicTags(XNode context, SqlStatement sqlStatement) {
        List<SqlNode> contents = new ArrayList<SqlNode>();
        NodeList children = context.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            XNode child = context.newXNode(children.item(i));
            String nodeName = child.getNode().getNodeName();
            if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE
                    || child.getNode().getNodeType() == Node.TEXT_NODE) {
                String data = child.getStringBody("");
                contents.add(new TextSqlNode(data));
            } else {
                NodeHandler handler = nodeHandlers.get(nodeName);
                if (handler == null) {
                    throw new ServiceException("Unknown element <"
                            + nodeName + "> in SQL statement.");
                }
                handler.handleNode(child, contents, sqlStatement);
            }
        }
        return contents;
    }

    private List<SqlNode> contents(XNode includeNode, SqlStatement sqlStatement) {
        return parseDynamicTags(includeNode, sqlStatement);
    }

    private interface NodeHandler {
        void handleNode(XNode nodeToHandle, List<SqlNode> targetContents, SqlStatement sqlStatement);
    }

    @SuppressWarnings("serial")
    private Map<String, NodeHandler> nodeHandlers = new HashMap<String, NodeHandler>() {
        {
            put("include", new IncludeNodeHandler());
            put("where", new WhereHandler());
            put("if", new IfHandler());
            put("choose", new ChooseHandler());
            put("when", new IfHandler());
            put("otherwise", new OtherwiseHandler());
            put("foreach", new ForEachHandler());
            put("unpivot", new UnpivotHandler());
            put("pivot", new PivotHandler());
        }
    };

    private class IncludeNodeHandler implements NodeHandler {
        @Override
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents, SqlStatement sqlStatement) {
            String refid = nodeToHandle.getStringAttribute("refid");
            XNode includeNode = sqlConfigBuilder.getSqlFragment(refid);
            if (includeNode == null) {

                if (includeNode == null) {
                    throw new ServiceException(
                            "Could not find SQL statement to include with refid '"
                                    + refid + "'");
                }
            }
            MixedSqlNode mixedSqlNode = new MixedSqlNode(contents(includeNode, sqlStatement));
            targetContents.add(mixedSqlNode);
        }
    }

    private class ForEachHandler implements NodeHandler {
        @Override
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents, SqlStatement sqlStatement) {
            List<SqlNode> sqlNodes = parseDynamicTags(nodeToHandle, sqlStatement);
            MixedSqlNode mixedSqlNode = new MixedSqlNode(sqlNodes);
            String collection = nodeToHandle.getStringAttribute("collection");
            String item = nodeToHandle.getStringAttribute("item");
            String index = nodeToHandle.getStringAttribute("index");
            String open = nodeToHandle.getStringAttribute("open");
            String close = nodeToHandle.getStringAttribute("close");
            String separator = nodeToHandle.getStringAttribute("separator");
            ForEachSqlNode forEachSqlNode = new ForEachSqlNode( mixedSqlNode, collection, index, item, open, close, separator);
            targetContents.add(forEachSqlNode);
        }
    }


    private class WhereHandler implements NodeHandler {
        @Override
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents, SqlStatement sqlStatement) {
            List<SqlNode> contents = parseDynamicTags(nodeToHandle, sqlStatement);
            MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
            WhereSqlNode where = new WhereSqlNode(mixedSqlNode);
            targetContents.add(where);
        }
    }

    private class IfHandler implements NodeHandler {
        @Override
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents, SqlStatement sqlStatement) {
            List<SqlNode> contents = parseDynamicTags(nodeToHandle, sqlStatement);
            MixedSqlNode mixedSqlNodes = new MixedSqlNode(contents);
            // String JdbcType = nodeToHandle.getStringAttribute("JdbcType");
            String test = nodeToHandle.getStringAttribute("test");
            IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNodes, test);
            targetContents.add(ifSqlNode);
        }
    }


    private class ChooseHandler implements NodeHandler {
        @Override
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents, SqlStatement sqlStatement) {
            List<SqlNode> whenSqlNodes = new ArrayList<>();
            List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
            handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes, sqlStatement);
            SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
            ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
            targetContents.add(chooseSqlNode);
        }

        private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes, SqlStatement sqlStatement) {
            List<XNode> children = chooseSqlNode.getChildren();
            for (XNode child : children) {
                String nodeName = child.getNode().getNodeName();
                NodeHandler handler = nodeHandlers.get(nodeName);
                if (handler instanceof IfHandler) {
                    handler.handleNode(child, ifSqlNodes, sqlStatement);
                } else if (handler instanceof OtherwiseHandler) {
                    handler.handleNode(child, defaultSqlNodes, sqlStatement);
                }
            }
        }


        private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
            SqlNode defaultSqlNode = null;
            if (defaultSqlNodes.size() == 1) {
                defaultSqlNode = defaultSqlNodes.get(0);
            } else if (defaultSqlNodes.size() > 1) {
                throw new ServiceException("Too many default (otherwise) elements in choose statement.");
            }
            return defaultSqlNode;
        }

    }

    private class OtherwiseHandler implements NodeHandler {
        @Override
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents, SqlStatement sqlStatement) {
            List<SqlNode> sqlNodes = parseDynamicTags(nodeToHandle, sqlStatement);
            MixedSqlNode mixedSqlNode = new MixedSqlNode(sqlNodes);
            targetContents.add(mixedSqlNode);
        }
    }

    private class UnpivotHandler implements NodeHandler {
        @Override
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents, SqlStatement sqlStatement) {
            String transformColumns = nodeToHandle.getStringAttribute("columns-to-rows");
            String columnsLabels = nodeToHandle.getStringAttribute("new-columns-labels");
            UnpivotModel unpivotModel = new UnpivotModel();
            unpivotModel.setTransformColumns(transformColumns);
            unpivotModel.setColumnsLabels(columnsLabels);
            sqlStatement.getResultProcessor().add(unpivotModel);
        }
    }


    private class PivotHandler implements NodeHandler {
        @Override
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents, SqlStatement sqlStatement) {

            PivotModel pivotModel = new PivotModel();
//            unpivotModel.setTransformColumns(transformColumns);
//            unpivotModel.setColumnsLabels(columnsLabels);
            sqlStatement.getResultProcessor().add(pivotModel);
        }
    }


}
